package cn.caplike.demo.nio._2021;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;

/**
 * <blockquote>
 *     Unicode: 用了两个字节来表示一个字符. UTF: Unicode Translation Format.
 *     <br>Unicode 是一种编码方式, 而 UTF 是一种存储方式;
 *     <br>2个字节标识一个字符,256的256次方.但是存储空间是个问题,欧美国家ASCII甚至ISO-8859-1就足够了.
 *     <br>- UTF-8  是 UTF 的实现方式之一, 解决对于英文字符的空间膨胀问题: 变长字节表示形式. 一般来说 UTF-8 用 3 个字节标识一个中文, 英文就是 1 个字节, 最多可以用 6 个字节标识一个字符.
 *              -> BOM (Byte Order Mark), 字节序标记 (0xFEFF(BE), 0xFFFE(LE)): 文件起始位置的文件头. 历史遗留问题 -> Windows
 *     <br>- UTF-16 是通过两个字节标识一个字符, 定长. UTF-16LE (little endian), UTF-16BE (big endian): 在文件头 - Zero Width No-Break Space, 零宽不换行的空格, 0xFEFF(BE), 0xFFFE(LE)
 * </blockquote>
 * <blockquote>
 *     ASCII(American Standard Code for Information Interchange): 7bit标识一个字符,共计128个,浪费了1bit
 *     <br>ISO-8859-1: 8bit标识一个字符,共计256,所有位都利用到
 *     <br>GB2312: 2个字节标识一个汉字,没有考虑生僻字
 *     <br>GBK: GB2312的超集
 *     <br>GB18030: 最完整的汉字标识形式
 *     <br>BIG5: 针对繁体中文
 * </blockquote>
 * @author LiKe
 * @version 1.0.0
 * @date 2022-02-26 14:41
 */
public class CharsetDemo {

    public static void main(String[] args) throws IOException {
        final String inputFile = CharsetDemo.class.getResource("/data/charset-demo-in.txt").getPath();
        final String outputFile = CharsetDemo.class.getResource("/data/charset-demo-out.txt").getPath();
        // String inputFile = "charset-demo-in.txt"; // CharsetDemo.class.getResource("/data/charset-demo-in.txt")).getPath()

        RandomAccessFile inputRandomAccessFile = new RandomAccessFile(inputFile, "r");
        RandomAccessFile outputRandomAccessFile = new RandomAccessFile(outputFile, "rw");

        long inputLength = new File(inputFile).length();

        FileChannel inputFileChannel = inputRandomAccessFile.getChannel();
        FileChannel outputFileChannel = outputRandomAccessFile.getChannel();

        MappedByteBuffer inputData = inputFileChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputLength); // [ ! ] 注意与传统的读入内存写出文件的方式的区别

        Charset charset = StandardCharsets.ISO_8859_1;    // 为什么 iso-8859-1 也能输出中文到目标文件: 不会丢失任何信息
        CharsetDecoder decoder = charset.newDecoder();
        CharsetEncoder encoder = charset.newEncoder();

        CharBuffer charBuffer = decoder.decode(inputData);  // 解码: 将文件解析/转换成字符串, 如果是 iso-8859-1, 这一步一定是乱码
        System.out.println(charBuffer.get(4)); // ä: "你" 的第一个字节
        ByteBuffer outputData = encoder.encode(charBuffer); // 编码: 将字符串转换成字节或者字节数组, 采用 iso-8859-1 进行编码, 将解码后的字节原封不动的输出到文件中,
        // 虽然过程间有乱码, 但是仍然将内容不丢失的输出到目标文件中 (中间只是起了个过度作用, 即使乱码也仍然没有丢失任何数据的输出到了目标文件中), 目标文件就是用 utf-8 格式的编码方式的, 所以就能正常显示中文
        // ByteBuffer outputData = Charset.forName("utf-8").encode(charBuffer) 就会出现乱码

        outputFileChannel.write(outputData);

        inputRandomAccessFile.close();
        outputRandomAccessFile.close();
    }
}
